﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DwarfParser
{
    class DwarfScanner
    {
        private int currCharIndex;
        private char currChar;
        private string text;

        private delegate bool ReadStringCondition();

        public void SetText(string text)
        {
            Reset();
            this.text = text;
        }

        public Token GetNextToken()
        {
            if (currCharIndex >= text.Length)
            {
                return new Token(TokenType.EOF);
            }

            UpdateCurrChar();
            Token t = Scan();
            ++currCharIndex;

            return t;
        }

        public Token GetForwardToken()
        {
            if (currCharIndex >= text.Length)
            {
                return new Token(TokenType.EOF);
            }

            // save current state
            int savedCurrCharIndex = currCharIndex;

            Token t = GetNextToken();

            // restore state
            currCharIndex = savedCurrCharIndex;
            UpdateCurrChar();

            return t;
        }

        public string GetCurrentPositionAsString()
        {
            return TextUtils.CharPosToText(text, currCharIndex);
        }

        private void Reset()
        {
            currCharIndex = 0;
        }

        private Token Scan()
        {
            SkipWhiteSpaces();
            return ReadToken();
        }

        private void SkipWhiteSpaces()
        {
            while (char.IsWhiteSpace(currChar))
            {
                PeekNext();
            }
        }

        private Token ReadToken()
        {
            switch (currChar)
            {
                case '=': return new Token(TokenType.ASSIGMENT);
                case ';': return new Token(TokenType.SEMICOLON);
                case '.': return new Token(TokenType.DOT);
                case ',': return new Token(TokenType.COMMA);
                case '(': return new Token(TokenType.LEFT_PAREN);
                case ')': return new Token(TokenType.RIGHT_PAREN);
                case '[': return new Token(TokenType.LEFT_BRACKET);
                case ']': return new Token(TokenType.RIGHT_BRACKET);
                case '{': return new Token(TokenType.LEFT_BRACE);
                case '}': return new Token(TokenType.RIGHT_BRACE);
                case '+': return new Token(TokenType.PLUS);
                case '-': return new Token(TokenType.MINUS);
                case '*': return new Token(TokenType.MULTIPLICATION);
                case '/': return SlashSwitchBranch();
                case '!': return new Token(TokenType.NOT);
                case '<': return new Token(TokenType.LT);
                case '>': return new Token(TokenType.GT);
                case '&': return AmpersandSwitchBranch();
                case '|': return PalkaSwitchBranch();

                default:  return DefaultSwitchBranch();
            }
        }

        private Token SlashSwitchBranch()
        {
            if (GetNextChar() == '/')
            {
                PeekNext();
                string commentBody = ReadToEndOfLine();
                return new Token(TokenType.COMMENT, commentBody);
            }
            else if (GetNextChar() == '*')
            {
                string commentBody = ReadToEndOfMultiLineComment();
                return new Token(TokenType.COMMENT, commentBody);
            }

            return new Token(TokenType.DIVISION);
        }

        private Token AmpersandSwitchBranch()
        {
            if (GetNextChar() == '&')
            {
                return new Token(TokenType.AND);
            }
                   
            return new Token(TokenType.AND_BITWISE);            
        }

        private Token PalkaSwitchBranch()
        {
            if (GetNextChar() == '|')
            {
                return new Token(TokenType.OR);
            }

            return new Token(TokenType.OR_BITWISE);
        }

        private Token DefaultSwitchBranch()
        {
            if (currChar.IsSimpleLatin())
            {
                return GetKeywordToken(ReadInditifier());
            }
            else if (char.IsDigit(currChar))
            {
                string str = ReadDigits();
                bool nextCharIsAlsoLatin = GetNextChar().IsSimpleLatin();
                if (!nextCharIsAlsoLatin)
                {
                    return new Token(TokenType.INTEGER, str);
                }                               
            }

            return GetErrorToken();
        }

        private Token GetErrorToken()
        {
            string errMsg = "at " + GetCurrentPositionAsString() + ": Unknown token";
            return new Token(TokenType.ERROR, errMsg);
        }

        private string ReadInditifier()
        {
            return ReadStringWhileCond(() => currChar.IsSimpleLatin() || char.IsDigit(currChar));
        }

        private string ReadDigits()
        {
            return ReadStringWhileCond(() => char.IsDigit(currChar));
        }

        private string ReadToEndOfLine()
        {
            string str = "";

            do
            {
                PeekNext();
                str += currChar;
            } while( (currChar != '\n' || currChar != '\n') && currCharIndex < text.Length - 1 );

            return str;
        }

        private string ReadToEndOfMultiLineComment()
        {
            string str = "";
            PeekNext();

            do
            {
                while (currChar != '*' && currCharIndex < text.Length)
                {
                    str += currChar;
                    PeekNext();
                }

            } while (GetNextChar() != '/' && currCharIndex < text.Length);

            return str;
        }

        private string ReadStringWhileCond(ReadStringCondition cond)
        {
            string str = "" + currChar;
            PeekNext();

            while (cond() && currCharIndex < text.Length - 1 )
            {
                str += currChar;
                PeekNext();
            }
            PeekPrev();

            return str;
        }

        private Token GetKeywordToken(string str)
        {
            switch (str)
            {
                case "this": return new Token(TokenType.THIS);
                case "new": return new Token(TokenType.NEW);
                case "return": return new Token(TokenType.RETURN);
                case "static": return new Token(TokenType.STATIC);                
                case "main": return new Token(TokenType.MAIN);
                case "class": return new Token(TokenType.CLASS);
                case "public": return new Token(TokenType.PUBLIC);
                case "if": return new Token(TokenType.IF);
                case "else": return new Token(TokenType.ELSE);
                case "while": return new Token(TokenType.WHILE);
                case "extends": return new Token(TokenType.EXTENDS);

                case "void": return new Token(TokenType.TYPE_DECL, str);
                case "int": return new Token(TokenType.TYPE_DECL, str);
                case "boolean": return new Token(TokenType.TYPE_DECL, str);
                case "String": return new Token(TokenType.TYPE_DECL, str);

                case "true": return new Token(TokenType.TRUE);
                case "false": return new Token(TokenType.FALSE);

                default: return new Token(TokenType.ID, str); ;
            }
        }
        
        private char GetNextChar()
        {
            return (currCharIndex + 1 < text.Length) ? text[currCharIndex + 1] : ' ';
        }

        private void PeekNext()
        {
            ++currCharIndex;
            UpdateCurrChar();
        }

        private void PeekPrev()
        {
            --currCharIndex;
            UpdateCurrChar();
        }

        private void UpdateCurrChar()
        {   
            currChar = text[currCharIndex];
        }
    }
}